﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationServer.Caching;

namespace Dexter.Caching.AppFabric {
	/// <summary>
	/// 	The AppFabric Cache provider
	/// </summary>
	public class AppFabricCacheProvider : ICacheProvider {
		readonly DataCache cacheRepository;

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "T:System.Object" /> class.
		/// </summary>
		public AppFabricCacheProvider ( AppFabricCacheFactory factory ) {
			this.cacheRepository = factory.CacheRepository;
		}

		#region ICacheProvider Members

		/// <summary>
		/// 	Adds new object into the cache repository
		/// </summary>
		/// <param name = "key">The cache key.</param>
		/// <param name = "value">The object to add.</param>
		/// <param name = "timeout">The timeout of the cache object.</param>
		/// <remarks>
		/// 	If the object will never expire, use <see cref = "TimeSpan.Zero" />
		/// </remarks>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		public void Add ( string key , object value , TimeSpan timeout ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			cacheRepository.Add ( key , value , timeout );
		}

		/// <summary>
		/// 	Adds new object into the cache repository
		/// </summary>
		/// <param name = "key">The cache key.</param>
		/// <param name = "value">The object to add.</param>
		/// <param name = "timeout">The timeout of the cache object.</param>
		/// <param name = "tags">The tags of the object (it cloud be used for search).</param>
		/// <remarks>
		/// 	If the object will never expire, use <see cref = "TimeSpan.Zero" />
		/// </remarks>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		public void Add ( string key , object value , TimeSpan timeout , string[] tags ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			cacheRepository.Add ( key , value , timeout , tags.Select ( x => new DataCacheTag ( x ) ) );
		}

		/// <summary>
		/// 	Adds new object into the cache repository
		/// </summary>
		/// <param name = "key">The cache key.</param>
		/// <param name = "value">The object to add.</param>
		/// <param name = "timeout">The timeout of the cache object.</param>
		/// <param name = "region">The region name.</param>
		/// <param name = "tags">The tags of the object (it cloud be used for search).</param>
		/// <remarks>
		/// 	If the object will never expire, use <see cref = "TimeSpan.Zero" />
		/// </remarks>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public void Add ( string key , object value , TimeSpan timeout , string region , string[] tags ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			cacheRepository.CreateRegion ( region );
			cacheRepository.Add ( key , value , timeout , tags.Select ( x => new DataCacheTag ( x ) ) , region );
		}

		/// <summary>
		/// 	If the specified object doesn't exist into the repository, it will be added. Otherwise it will update the existing object.
		/// </summary>
		/// <param name = "key">The cache key.</param>
		/// <param name = "value">The object to add.</param>
		/// <param name = "timeout">The timeout of the cache object.</param>
		/// <remarks>
		/// 	If the object will never expire, use <see cref = "TimeSpan.Zero" />
		/// </remarks>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		public void Put ( string key , object value , TimeSpan timeout ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			cacheRepository.Put ( key , value , timeout );
		}

		/// <summary>
		/// 	If the specified object doesn't exist into the repository, it will be added. Otherwise it will update the existing object.
		/// </summary>
		/// <param name = "key">The cache key.</param>
		/// <param name = "value">The object to add.</param>
		/// <param name = "timeout">The timeout of the cache object.</param>
		/// <param name = "tags">The tags of the object (it cloud be used for search).</param>
		/// <remarks>
		/// 	If the object will never expire, use <see cref = "TimeSpan.Zero" />
		/// </remarks>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		public void Put ( string key , object value , TimeSpan timeout , string[] tags ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			cacheRepository.Put ( key , value , timeout , tags.Select ( x => new DataCacheTag ( x ) ) );
		}

		/// <summary>
		/// 	If the specified object doesn't exist into the repository, it will be added. Otherwise it will update the existing object.
		/// </summary>
		/// <param name = "key">The cache key.</param>
		/// <param name = "value">The object to add.</param>
		/// <param name = "timeout">The timeout of the cache object.</param>
		/// <param name = "region">The region name.</param>
		/// <param name = "tags">The tags of the object (it cloud be used for search).</param>
		/// <remarks>
		/// 	If the object will never expire, use <see cref = "TimeSpan.Zero" />
		/// </remarks>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public void Put ( string key , object value , TimeSpan timeout , string region , string[] tags ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			cacheRepository.CreateRegion ( region );
			cacheRepository.Put ( key , value , timeout , tags.Select ( x => new DataCacheTag ( x ) ) , region );
		}

		/// <summary>
		/// 	Return a set of cache objects with only one round-trip
		/// </summary>
		/// <param name = "keys">All the cache key.</param>
		/// <param name = "region">The cache region.</param>
		/// <returns>A collection of cache objects.</returns>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "keys" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public IEnumerable <KeyValuePair <string , object>> BulkGet ( IEnumerable <string> keys , string region ) {
			if (keys == null) {
				throw new ArgumentNullException ( "keys" );
			}
			
			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return cacheRepository.BulkGet ( keys , region );
		}

		/// <summary>
		/// 	Remove all cache objects associated to the specified cache region.
		/// </summary>
		/// <param name = "region">Name of the region.</param>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public void ClearRegion ( string region ) {
			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			cacheRepository.ClearRegion ( region );
		}

		/// <summary>
		/// 	Retrieve the cache object for the specified cache key.
		/// </summary>
		/// <param name = "key">The key.</param>
		/// <returns>The cache object</returns>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		public object Get ( string key ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			return cacheRepository.Get ( key );
		}

		/// <summary>
		/// 	Retrieve the cache object for the specified cache key and specified region.
		/// </summary>
		/// <param name = "key">The key.</param>
		/// <param name = "region">The region name.</param>
		/// <returns>The cache object</returns>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public object Get ( string key , string region ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return cacheRepository.Get ( key , region );
		}

		/// <summary>
		/// 	Gets the specified key.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "key">The key.</param>
		/// <returns></returns>
		public T Get <T> ( string key ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			return ( T ) cacheRepository.Get ( key );
		}

		/// <summary>
		/// 	Gets the specified key.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "key">The key.</param>
		/// <param name = "region">The region.</param>
		/// <returns></returns>
		public T Get <T> ( string key , string region ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return ( T ) cacheRepository.Get ( key , region );
		}

		/// <summary>
		/// 	Retrieve all cache object with the specified tag and the specified region.
		/// </summary>
		/// <param name = "tag">The tags name.</param>
		/// <param name = "region">The region name.</param>
		/// <returns>A set of cache object.</returns>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "tag" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "tag" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public IEnumerable <KeyValuePair <string , object>> GetObjectsByTag ( string tag , string region ) {
			if ( tag == null ) {
				throw new ArgumentNullException ( "tag" );
			}

			if ( tag == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "tag" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return cacheRepository.GetObjectsByTag ( new DataCacheTag ( tag ) , region );
		}

		/// <summary>
		/// 	Returns a list of objects that have tags matching all of the tags provided in the parameter of this method.
		/// </summary>
		/// <param name = "tags">The tags name.</param>
		/// <param name = "region">The region name.</param>
		/// <returns>A set of cache object.</returns>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "tags" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public IEnumerable <KeyValuePair <string , object>> GetObjectsByAllTags ( string[] tags , string region ) {
			if ( tags == null ) {
				throw new ArgumentNullException ( "tags" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return cacheRepository.GetObjectsByAllTags ( tags.Select ( x => new DataCacheTag ( x ) ) , region );
		}

		/// <summary>
		/// 	Returns a list of objects that have tags matching any of the tags provided in the parameter of this method.
		/// </summary>
		/// <param name = "tags">The tags.</param>
		/// <param name = "region">The region.</param>
		/// <returns>A set of cache object.</returns>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "tags" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public IEnumerable <KeyValuePair <string , object>> GetObjectsByAnyTags ( string[] tags , string region ) {
			if ( tags == null ) {
				throw new ArgumentNullException ( "tags" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return cacheRepository.GetObjectsByAnyTag ( tags.Select ( x => new DataCacheTag ( x ) ) , region );
		}

		/// <summary>
		/// 	Returns a list of all objects in a region. This method is useful when you do not know all the tags used in the region.
		/// </summary>
		/// <param name = "region">The region name.</param>
		/// <returns>A set of cache object.</returns>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public IEnumerable <KeyValuePair <string , object>> GetObjectsInRegion ( string region ) {
			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			return cacheRepository.GetObjectsInRegion ( region );
		}

		/// <summary>
		/// 	Remove a cache object with the specified key from the cache repository.
		/// </summary>
		/// <param name = "key">The key.</param>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		public void Remove ( string key ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			cacheRepository.Remove ( key );
		}

		/// <summary>
		/// 	Remove a cache object with the specified key and the specified region from the cache repository.
		/// </summary>
		/// <param name = "key">The key.</param>
		/// <param name = "region">The region.</param>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "key" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "key" /> is null.</exception>
		/// <exception cref = "ArgumentException">Will be throw if <paramref name = "region" /> is empty.</exception>
		/// <exception cref = "ArgumentNullException">Will be throw if <paramref name = "region" /> is null.</exception>
		public void Remove ( string key , string region ) {
			if ( key == null ) {
				throw new ArgumentNullException ( "key" );
			}

			if ( key == string.Empty ) {
				throw new ArgumentException ( "Invalid cache key." , "key" );
			}

			if ( region == null ) {
				throw new ArgumentNullException ( "region" );
			}

			if ( region == string.Empty ) {
				throw new ArgumentException ( "Invalid cache region." , "region" );
			}

			cacheRepository.Remove ( key , region );
		}

		#endregion
	}
}